/*
 *  This file is part of the Jikes RVM project (http://jikesrvm.org).
 *
 *  This file is licensed to You under the Eclipse Public License (EPL);
 *  You may not use this file except in compliance with the License. You
 *  may obtain a copy of the License at
 *
 *      http://www.opensource.org/licenses/eclipse-1.0.php
 *
 *  See the COPYRIGHT.txt file distributed with this work for information
 *  regarding copyright ownership.
 */
package org.mmtk.plan.compressor;

import org.mmtk.plan.*;
import org.mmtk.policy.LargeObjectLocal;

import org.mmtk.policy.compressor.CompressorMetadata;
import org.mmtk.policy.compressor.CopyingScanner;
import org.mmtk.policy.compressor.MarkCopyLocal;
import org.mmtk.policy.compressor.MetadataComputingScanner;

import org.mmtk.utility.Log;

import org.mmtk.utility.alloc.LinearScan;
import org.mmtk.vm.VM;

import org.vmmagic.unboxed.*;
import org.vmmagic.pragma.*;

/**
 * This class implements <i>per-collector thread</i> behavior
 * and state for the <i>SS</i> plan, which implements a full-heap
 * semi-space collector.<p>
 *
 * Specifically, this class defines <i>SS</i> collection behavior
 * (through <code>trace</code> and the <code>collectionPhase</code>
 * method), and collection-time allocation (copying of objects).<p>
 *
 * See {@link Compressor} for an overview of the semi-space algorithm.<p>
 *
 * @see Compressor
 * @see CompressorMutator
 * @see StopTheWorldCollector
 * @see CollectorContext
 */
@Uninterruptible
public class CompressorCollector extends StopTheWorldCollector {

  public static boolean VERBOSE = true;
  
  /****************************************************************************
   * Instance fields
   */

  protected final MarkCopyLocal markCopyLocal = new MarkCopyLocal();
  protected final LargeObjectLocal los = new LargeObjectLocal(Plan.loSpace);

  protected final CompressorTraceLocal markTrace;
  protected final CompressorForwardTraceLocal forwardTrace;
  protected TraceLocal currentTrace;
  
  protected LinearScan copyingScanner;
  protected final LinearScan computingScanner;
  
  /****************************************************************************
   *
   * Initialization
   */

  public CompressorCollector() {
    this.markTrace = new CompressorTraceLocal(global().markTrace);
    this.forwardTrace = new CompressorForwardTraceLocal(global().forwardTrace, this);
    this.copyingScanner = new CopyingScanner(this);
    this.computingScanner = new MetadataComputingScanner();
  }
  
  public Address getBase() {
   return markCopyLocal.getInitialRegion();
  }

  /****************************************************************************
   *
   * Collection-time allocation
   */

  /**
   * Allocate space for copying an object (this method <i>does not</i>
   * copy the object, it only allocates space)
   *
   * @param original A reference to the original object
   * @param bytes The size of the space to be allocated (in bytes)
   * @param align The requested alignment.
   * @param offset The alignment offset.
   * @return The address of the first byte of the allocated region
   */
  @Override
  @Inline
  public Address allocCopy(ObjectReference original, int bytes,
      int align, int offset, int allocator) {
    if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(allocator == Compressor.ALLOC_IMMORTAL);
    // Clear live bits in the original object so that we don't copy it again
    CompressorMetadata.clearLiveBits(original);
    return immortal.alloc(bytes, align, offset);
  }

  /**
   * Perform any post-copy actions.
   *
   * @param object The newly allocated object
   * @param typeRef the type reference for the instance being created
   * @param bytes The size of the space to be allocated (in bytes)
   */
  @Override
  @Inline
  public void postCopy(ObjectReference object, ObjectReference typeRef,
      int bytes, int allocator) {
    if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(allocator == Compressor.ALLOC_IMMORTAL);
    Compressor.immortalSpace.initializeHeader(object);
  }

  /****************************************************************************
   *
   * Collection
   */

  /**
   * Perform a per-collector collection phase.
   *
   * @param phaseId The collection phase to perform
   * @param primary Perform any single-threaded activities using this thread.
   */
  @Inline
  public void collectionPhase(short phaseId, boolean primary) {
    if (phaseId == Compressor.PREPARE) {
      currentTrace = markTrace;
      
      // Rebind the copy bump pointer to the appropriate semispace.
      if(VERBOSE) {
        Log.write("Rebinding markCopyLocal\n");
      }
      
      markCopyLocal.rebind(Compressor.toSpace());
      los.prepare(true);
      markTrace.prepare();
      
      super.collectionPhase(phaseId, primary);
      return;
    }
    
    if (phaseId == Compressor.CLOSURE) {
      // marking
      if(VERBOSE) { 
        Log.write("Before Complete Trace"); Log.writeln();   
      }
      
      markTrace.completeTrace();
      
      if(VERBOSE) { 
        Log.write("After Complete Trace"); Log.writeln(); 
      }

      return;
    }
    
    if (phaseId == Compressor.CALC_METADATA) {
      // Marking
      if(VERBOSE) { 
        Log.write("[Collector] Before calculating metadata "); Log.write(getId()); Log.writeln();
      }
      
      /*
       * Calculate an the offset and first object vectors using a linear scanner.
       * The offset vector uses the markbits in the metadata space.
       * For each block, there is an entry in the offset vector holding a pointer to 
       * the location in to-virtual-space to which the first object (that start) 
       * in the block is moved.
       * The first object vector ...
       */
      
      while (!global().calculateHeads.isEmpty()) {
        if(VERBOSE) {
          Log.write("[CALC_METADATA] Dequeuing from BumpPointer start addr queue"); Log.writeln();
        }
        Address bumpStart = global().calculateHeads.pop();
        if (VERBOSE) {
          Log.write("Address: "); Log.write(bumpStart); Log.writeln();
        }
      
        MarkCopyLocal.linearScan(computingScanner, bumpStart);
        global().preallocCopyHeads.add(bumpStart);
      }
      
      if(VERBOSE) { 
        Log.write("[Collector] After calculating metadata "); Log.write(getId()); Log.writeln(); 
      }
      
      return;
    }
        
    if (phaseId == Compressor.PREALLOC_COPY) {

      markTrace.release();
      los.release(true);

      VM.assertions._assert(!global().preallocCopyHeads.isEmpty());
      while (!global().preallocCopyHeads.isEmpty()) {
        if(VERBOSE) {
          Log.write("[PREALLOC_COPY] Dequeuing from BumpPointer start addr queue"); Log.writeln();
        }
        Address bumpStart = global().preallocCopyHeads.pop();
        if (VERBOSE) {
          Log.write("Address: "); Log.write(bumpStart); Log.writeln();
        }
        
        // Get total live data and preallocate
        int totalLiveData = MarkCopyLocal.getTotalLiveData(bumpStart);
        if(VERBOSE) {
          Log.write("totalLiveData["); Log.write(bumpStart); Log.write("] = "); 
          Log.write(totalLiveData); Log.writeln();
     
          Log.write("Preallocating "); Log.write(totalLiveData); Log.write(" bytes"); Log.writeln();
        }

        // Preallocated the memory to copy objects to
        Address start = markCopyLocal.prealloc(Extent.fromIntZeroExtend(totalLiveData));
        
        // Setup cursor so that everything will be copied to the address preallocated afterwards
        copyingScanner.init(start);
        
        if(VERBOSE) {
          markCopyLocal.show();
        }
        
        // Scan region and copy to preallocated
        if(VERBOSE) {
          Log.write("LinearScan with CopyingScanner"); Log.writeln();
        }
        MarkCopyLocal.linearScan(copyingScanner, bumpStart);
      }
      
      if(VERBOSE) {
        Log.write("Putting new initialRegion in queue "); 
        Log.write(markCopyLocal.getInitialRegion()); Log.writeln();
      }
      
      markCopyLocal.setDataEnd();
      // Copy new bump pointer head into global queue for use by mutators
      global().allocationHeads.add(markCopyLocal.getInitialRegion());
      
      return;
    }
    
    if (phaseId == Compressor.RELEASE) {
      markTrace.release();
      los.release(true);
      super.collectionPhase(phaseId, primary);
      return;
    }
    
    if (phaseId == Compressor.PREPARE_FORWARD) {
      currentTrace = forwardTrace;
      
      super.collectionPhase(Compressor.PREPARE, primary);
      forwardTrace.prepare();
      return;
    }
    
    if (phaseId == Compressor.FORWARD_CLOSURE) {
      forwardTrace.completeTrace();
      return;
    }

    if (phaseId == Compressor.RELEASE_FORWARD) {
      forwardTrace.release();
      super.collectionPhase(Compressor.RELEASE, primary);
      
      currentTrace = markTrace;
      return;
    }


    super.collectionPhase(phaseId, primary);
  }
  
  
  /****************************************************************************
  *
  * Auxillary data structures
  */
  
  /****************************************************************************
   *
   * Miscellaneous
   */

  /** @return The active global plan as an <code>Compressor</code> instance. */
  @Inline
  private static Compressor global() {
    return (Compressor) VM.activePlan.global();
  }

  /** @return the current trace object. */
  public TraceLocal getCurrentTrace() {
    return currentTrace;
  }
}
